package com.chenke.subject.infra.basic.es;

import com.alibaba.fastjson.JSON;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.http.HttpHost;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.search.SearchType;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.reindex.BulkByScrollResponse;
import org.elasticsearch.index.reindex.DeleteByQueryRequest;
import org.elasticsearch.search.Scroll;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.FetchSourceContext;
import org.elasticsearch.search.sort.ScoreSortBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.*;

@Component
@Slf4j
public class EsRestClient {

    //存放集群链接的map name为集群name
    public static Map<String, RestHighLevelClient> clientMap = new HashMap<>();

    //配置了链接的类
    @Resource
    private EsConfigProperties esConfigProperties;

    private static final RequestOptions COMMON_OPTIONS;

    static {
        RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder();
        COMMON_OPTIONS = builder.build();
    }

    @PostConstruct
    public void initialize() {
        List<EsClusterConfig> esConfigs = esConfigProperties.getEsConfigs();
        for (EsClusterConfig esConfig : esConfigs) {
            log.info("initialize.config.name:{},node:{}", esConfig.getName(), esConfig.getNodes());
            RestHighLevelClient restHighLevelClient = initRestClient(esConfig);
            if (restHighLevelClient != null) {
                clientMap.put(esConfig.getName(), restHighLevelClient);
            } else {
                log.error("config.name:{},node:{}.initError", esConfig.getName(), esConfig.getNodes());
            }
        }
    }

    private RestHighLevelClient initRestClient(EsClusterConfig esClusterConfig) {

        String[] ipPortArr = esClusterConfig.getNodes().split(",");
        List<HttpHost> httpHostList = new ArrayList<>(ipPortArr.length);
        for (String ipPort : ipPortArr) {
            String[] ipPortInfo = ipPort.split(":");
            if (ipPortInfo.length == 2) {
                HttpHost httpHost = new HttpHost(ipPortInfo[0], NumberUtils.toInt(ipPortInfo[1]));
                httpHostList.add(httpHost);
            }
        }
        HttpHost[] httpHosts = new HttpHost[httpHostList.size()];
        httpHostList.toArray(httpHosts);

        RestClientBuilder builder = RestClient.builder(httpHosts);
        RestHighLevelClient restHighLevelClient = new RestHighLevelClient(builder);
        return restHighLevelClient;
    }

    private static RestHighLevelClient getClient(String clusterName) {
        return clientMap.get(clusterName);
    }

    /**
     * 插入文档
     **/
    @SneakyThrows
    public static boolean insertDoc(EsIndexInfo esIndexInfo, EsSourceData esSourceData) {
        IndexRequest indexRequest = new IndexRequest(esIndexInfo.getIndexName());
        indexRequest.source(esSourceData.getData());
        indexRequest.id(esSourceData.getDocId());
        getClient(esIndexInfo.getClusterName()).index(indexRequest, COMMON_OPTIONS);
        return true;
    }

    /**
     * 修改文档
     **/
    @SneakyThrows
    public static boolean updateDoc(EsIndexInfo esIndexInfo, EsSourceData esSourceData) {
        UpdateRequest updateRequest = new UpdateRequest();
        updateRequest.index(esIndexInfo.getIndexName());
        updateRequest.id(esSourceData.getDocId());
        updateRequest.doc(esSourceData.getData());
        getClient(esIndexInfo.getClusterName()).update(updateRequest, COMMON_OPTIONS);
        return true;
    }

    @SneakyThrows
    public static boolean batchUpdateDoc(EsIndexInfo esIndexInfo,
                                         List<EsSourceData> esSourceDataList) {

        boolean flag = false;
        BulkRequest bulkRequest = new BulkRequest();
        for (EsSourceData esSourceData : esSourceDataList) {
            String docId = esSourceData.getDocId();
            if (StringUtils.isNotBlank(docId)) {
                UpdateRequest updateRequest = new UpdateRequest();
                updateRequest.index(esIndexInfo.getIndexName());
                updateRequest.id(esSourceData.getDocId());
                updateRequest.doc(esSourceData.getData());
                bulkRequest.add(updateRequest);
                flag = true;
            }
        }
        if (flag) {
            BulkResponse bulk = getClient(esIndexInfo.getClusterName()).bulk(bulkRequest, COMMON_OPTIONS);
            if (bulk.hasFailures()) {
                return false;
            }
        }
        return true;
    }

    /**
     * 删除所有index
     * **/
    @SneakyThrows
    public static boolean delete(EsIndexInfo esIndexInfo){
        DeleteByQueryRequest deleteByQueryRequest =
                new DeleteByQueryRequest(esIndexInfo.getIndexName());
        deleteByQueryRequest.setQuery(QueryBuilders.matchAllQuery());
        BulkByScrollResponse response = getClient(esIndexInfo.getClusterName()).deleteByQuery(
                deleteByQueryRequest, COMMON_OPTIONS
        );
        long deleted = response.getDeleted();
        return true;
    }

    @SneakyThrows
    public static boolean deleteDoc(EsIndexInfo esIndexInfo, String docId) {
            DeleteRequest deleteRequest = new DeleteRequest(esIndexInfo.getIndexName());
            deleteRequest.id(docId);
            DeleteResponse response = getClient(esIndexInfo.getClusterName()).delete(deleteRequest, COMMON_OPTIONS);
            log.info("deleteDoc.response:{}", JSON.toJSONString(response));
            return true;
    }
    public static boolean isExistDocById(EsIndexInfo esIndexInfo, String docId) {
        try {
            GetRequest getRequest = new GetRequest(esIndexInfo.getIndexName());
            getRequest.id(docId);
            return getClient(esIndexInfo.getClusterName()).exists(getRequest, COMMON_OPTIONS);
        } catch (Exception e) {
            log.error("isExistDocById.exception:{}", e.getMessage(), e);
        }
        return false;
    }

    public static Map<String, Object> getDocById(EsIndexInfo esIndexInfo, String docId) {
        try {
            GetRequest getRequest = new GetRequest(esIndexInfo.getIndexName());
            getRequest.id(docId);
            GetResponse response = getClient(esIndexInfo.getClusterName()).get(getRequest, COMMON_OPTIONS);
            Map<String, Object> source = response.getSource();
            return source;
        } catch (Exception e) {
            log.error("isExistDocById.exception:{}", e.getMessage(), e);
        }
        return null;
    }

    public static Map<String, Object> getDocById(EsIndexInfo esIndexInfo, String docId,
                                                 String[] fields) {
        try {
            GetRequest getRequest = new GetRequest(esIndexInfo.getIndexName());
            getRequest.id(docId);
            FetchSourceContext fetchSourceContext = new FetchSourceContext(true, fields, null);
            getRequest.fetchSourceContext(fetchSourceContext);
            GetResponse response = getClient(esIndexInfo.getClusterName()).get(getRequest, COMMON_OPTIONS);
            Map<String, Object> source = response.getSource();
            return source;
        } catch (Exception e) {
            log.error("isExistDocById.exception:{}", e.getMessage(), e);
        }
        return null;
    }

    @SneakyThrows
    public static SearchResponse searchWithTermQuery(EsIndexInfo esIndexInfo,
                                                     EsSearchRequest esSearchRequest)
    {
        BoolQueryBuilder bq = esSearchRequest.getBq();
        String[] fields = esSearchRequest.getFields();
        int from = esSearchRequest.getFrom();
        int size = esSearchRequest.getSize();
        Long minutes = esSearchRequest.getMinutes();
        Boolean needScroll = esSearchRequest.getNeedScroll();
        String sortName = esSearchRequest.getSortName();
        SortOrder sortOrder = esSearchRequest.getSortOrder();

        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.query(bq);
        searchSourceBuilder.fetchSource(fields, null)
                .from(from)
                .size(size);

        if (Objects.nonNull(esSearchRequest.getHighlightBuilder())) {
            searchSourceBuilder.highlighter(esSearchRequest.getHighlightBuilder());
        }

        if (StringUtils.isNotBlank(sortName)) {
            searchSourceBuilder.sort(sortName);
        }
        searchSourceBuilder.sort(new ScoreSortBuilder().order(sortOrder));

        SearchRequest searchRequest = new SearchRequest();
        searchRequest.searchType(SearchType.DEFAULT);
        searchRequest.indices(esIndexInfo.getIndexName());
        searchRequest.source(searchSourceBuilder);

        if (needScroll) {
            Scroll scroll = new Scroll(TimeValue.timeValueMinutes(minutes));
            searchRequest.scroll(scroll);
        }

        SearchResponse search = getClient(esIndexInfo.getClusterName()).search(searchRequest, COMMON_OPTIONS);
        return search;

    }


}